package com.code.google.mdsc.io.csv.impl;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;

import com.code.google.mdsc.exception.CSVParserException;
import com.code.google.mdsc.io.csv.CSVBookingParser;
import com.code.google.mdsc.io.csv.impl.threading.CSVBookingParserTask;
import com.code.google.mdsc.io.log.SimpleLogger;
import com.code.google.mdsc.io.log.impl.SimpleLoggerImpl;
import com.code.google.mdsc.model.Booking;
import com.code.google.mdsc.model.Creditor;
import com.code.google.mdsc.model.Debtor;
import com.code.google.mdsc.model.impl.BookingImpl;
import com.code.google.mdsc.model.impl.CreditorImpl;
import com.code.google.mdsc.model.impl.DebtorImpl;

public class CSVBookingParserImpl implements CSVBookingParser {

	private static SimpleLogger logger = SimpleLoggerImpl.getInstance();
	private static final int VALID_CSV_DEBTOR_LENGTH = 8;

	private static final int VALID_CSV_CREDITOR_LENGTH = 7;
	private int approximateLineNumber = -1;
	private final CSVBookingParserTask csvbpt;
	private DecimalFormat decimalFormat = new DecimalFormat(",##0.00");

	public CSVBookingParserImpl(CSVBookingParserTask csvbpt) {
		this.csvbpt = csvbpt;
	}

	private int getApproximateLineNumbers(File in) {

		if (!(approximateLineNumber == -1))
			return approximateLineNumber;

		LineNumberReader lnr = null;
		try {
			lnr = new LineNumberReader(new FileReader(in));
			lnr.skip(Long.MAX_VALUE);
		} catch (FileNotFoundException e) {
			logger.log(SimpleLogger.LogMode.ERROR, e.getMessage());
			throw new CSVParserException(e);
		} catch (IOException e) {
			logger.log(SimpleLogger.LogMode.ERROR, e.getMessage());
			throw new CSVParserException(e);
		} finally {
			if (lnr != null) {
				try {
					lnr.close();
				} catch (IOException e) {
					logger.log(SimpleLogger.LogMode.ERROR, e.getMessage());
					throw new CSVParserException(e);
				}
			}
		}
		approximateLineNumber = lnr.getLineNumber();
		return approximateLineNumber;

	}

	@Override
	public Booking parse(File in) {
		if (in == null)
			throw new CSVParserException(new IllegalArgumentException(
					"the input file must not be null"));

		getApproximateLineNumbers(in);

		Booking booking = null;
		BufferedReader csvReader = null;

		try {
			csvReader = new BufferedReader(new FileReader(in));
			boolean debtorData = false;
			boolean creditorData = true;
			String line = "";
			Creditor creditor = null;
			GregorianCalendar dateOfBooking = new GregorianCalendar();
			List<Debtor> debtors = new ArrayList<Debtor>();
			float progress = 0;
			int i = -1;
			while ((line = csvReader.readLine()) != null) {
				i++;

				logger.log(SimpleLogger.LogMode.DEBUG, "HERE WE GO:" + i + "/"
						+ approximateLineNumber);
				if (line.contains("#")) {
					debtorData = true;
					creditorData = false;
					continue;
				}

				line = line.replaceAll("\\\"", "");

				String[] lineSplitted = line.split(",");
				if (creditorData) {

					if (lineSplitted.length != VALID_CSV_CREDITOR_LENGTH)
						throw new CSVParserException(
								new IllegalArgumentException(),
								"the line does not contain the specified information. take a look at the csv input specification");

					creditor = parseCreditor(lineSplitted);

					dateOfBooking.setTime(DateFormat.getDateInstance().parse(
							lineSplitted[VALID_CSV_CREDITOR_LENGTH - 1]));
					progress += (100.0 / approximateLineNumber);
					updateProgress(progress);
					continue;
				}

				if (debtorData) {
					if (lineSplitted.length != VALID_CSV_DEBTOR_LENGTH)
						throw new CSVParserException(
								new IllegalArgumentException(),
								"the line does not contain the specified information. take a look at the csv input specification");
					debtors.add(parseDebtor(lineSplitted));
					progress += (100.0 / approximateLineNumber);
					updateProgress(progress);
					continue;
				}

			}

			booking = new BookingImpl(creditor, debtors, dateOfBooking);
		} catch (FileNotFoundException e) {
			logger.log(SimpleLogger.LogMode.ERROR, e.getMessage());
			throw new CSVParserException(e);
		} catch (IOException e) {
			logger.log(SimpleLogger.LogMode.ERROR, e.getMessage());
			throw new CSVParserException(e);
		} catch (ParseException e) {
			logger.log(SimpleLogger.LogMode.ERROR, e.getMessage());
			throw new CSVParserException(e);
		} finally {
			if (csvReader != null) {
				try {
					csvReader.close();
				} catch (IOException e) {
					throw new CSVParserException(e);
				}
			}
		}
		return booking;
	}

	// creditorIdentifier,creditorCountry,name,bic,iban,adress,dateOfBooking
	private Creditor parseCreditor(String[] lineSplitted) {
		Creditor creditor = new CreditorImpl(lineSplitted[0], lineSplitted[1],
				lineSplitted[4], lineSplitted[3], lineSplitted[2]);
		creditor.addAdressLine(lineSplitted[5]);
		return creditor;
	}

	// accountHolder,bookingVolumne,bic,iban,mandatoryNumber,purpose,mandatorySignatureDate
	private Debtor parseDebtor(String[] lineSplitted) throws ParseException {
		GregorianCalendar gc = new GregorianCalendar();
		gc.setTimeZone(TimeZone.getTimeZone("CET"));
		gc.setTime(DateFormat.getDateInstance().parse(lineSplitted[6]));

		Debtor debtor = new DebtorImpl(lineSplitted[2], lineSplitted[3],
				lineSplitted[4], lineSplitted[0], new BigDecimal(decimalFormat
						.format(Double.parseDouble(lineSplitted[1]) / 100.0)
						.replace(",", ".")), gc, lineSplitted[5],
				lineSplitted[7]);
		return debtor;
	}

	private void updateProgress(float progress) {

		csvbpt.updateProgress((int) progress);
	}
}
